package nina.yiyou.attendance.s10merge.compute.latepoint

import nina.yiyou.attendance.s10merge.compute.AttendDetailMerger
import nina.yiyou.attendance.s10merge.input.AttendLeaveExcelReader

import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.temporal.ChronoUnit
import java.util.function.Function

class LatePointCalculator {

  interface DefaultPoint {

    LocalDateTime amIn()

    LocalDateTime amOut()

    LocalDateTime pmIn()
  }

  LatePointCalculator(AttendDetailMerger.PunchDay day, List<AttendDetailMerger.PunchDay> dayList, DefaultPoint defaultPoint) {
    _day = day
    _dayList = dayList
    _defaultPoint = defaultPoint
  }

  LocalDateTime calc() {
    LocalDate curDate = _day.date()
    LocalTime overridePoint = _day.overrideLatePoint()
    if (overridePoint) {
      return curDate.atTime(overridePoint)
    }

    List leaveList = _day.leaveList()
    if (leaveList) {
      // 请假会影响迟到线
      return handleLeave(leaveList, _defaultPoint)
    }

    // 至少要来一个钟才算早上有上班
    def isAmIn = {
      assert _day.punchIn()
      _day.punchIn().isBefore(_defaultPoint.amOut().minusHours(1))
    }

    LocalDateTime am10 = curDate.atTime(10, 00)
    LocalDate prevDate = curDate.minusDays(1)

    def calcImpl = { AttendDetailMerger.PunchDay prev ->
      LocalDateTime prevOut = prev.punchOut()
      if (!prevOut) {
        return _defaultPoint.amIn()
      }

      def isOutAfter = { LocalDateTime t -> !prevOut.isBefore(t) }
      if (isOutAfter(curDate.atTime(7, 00))) {
        return curDate.atTime(LocalTime.MAX.truncatedTo(ChronoUnit.SECONDS))
      }
      if (isOutAfter(curDate.atTime(0, 00))) {
        return isAmIn() ? am10 : _defaultPoint.pmIn()
      }
      if (isOutAfter(prevDate.atTime(21, 00))) {
        return am10
      }

      // 早下班隔天要早来
      return curDate.atTime(9, 30)
    }

    return new PreviousDayFinder(_day, _dayList).find()
        .map(calcImpl as Function<AttendDetailMerger.PunchDay, LocalDateTime>)
        .orElse(_defaultPoint.amIn())
  }

  private LocalDateTime handleLeave(List<AttendLeaveExcelReader.Line> leaveList, DefaultPoint point) {
    def getPoint = { LocalDateTime end ->
      if (end == point.amOut()) {
        return point.pmIn()
      }
      return end
    }

    return leaveList.stream()
        .map { it.endTime() }
        .max(Comparator.comparing { it as LocalDateTime })
        .map { getPoint(it) }
        .orElse(point.amIn())
  }

  private final AttendDetailMerger.PunchDay _day

  private final List<AttendDetailMerger.PunchDay> _dayList
  private final DefaultPoint _defaultPoint
}
